package top.jnaw.jee.utils;

import com.alibaba.fastjson.JSONArray;
import com.jfinal.kit.PropKit;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Db;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.subject.Subject;
import top.jnaw.jee.plugin.shiro.ShiroKit;
import top.jnaw.jee.utils.loginUtils.EasyTypeToken;

public class Shiros {

  private static String SEPARATOR = ":";

  private static String SQL_ADD_USER = "INSERT IGNORE INTO users"
      + " (username, password, password_salt) VALUES (?, ?, ?)";
  private static String SQL_SET_USER = "UPDATE users"
      + " SET password = ?, password_salt = ? WHERE username = ?";
  private static String SQL_SET_PWD = "UPDATE users"
      + " SET password = ?, password_salt = ? WHERE username = ?";
  private static String SQL_DELETE_USER = "DELETE FROM users"
      + " WHERE username = ?";

  private static String SQL_CLEAN_ROLES = "DELETE FROM user_roles"
      + " WHERE username = ?";
  private static String SQL_CLEAN_USERS = "DELETE FROM user_roles"
      + " WHERE role_name = ?";
  private static String SQL_CLEAN_PERMISSIONS = "DELETE FROM roles_permissions"
      + " WHERE role_name = ?";

  private static String SQL_SET_ROLES = "INSERT IGNORE INTO user_roles"
      + " (username, role_name) VALUES (?, ?)";
  private static String SQL_SET_USERS = "INSERT IGNORE INTO user_roles"
      + " (role_name,username) VALUES (?, ?)";
  private static String SQL_SET_PERMISSIONS =
      "INSERT IGNORE INTO roles_permissions"
          + " (role_name, permission) VALUES (?, ?)";

  private static String SQL_GET_ROLES = "SELECT role_name"
      + " FROM user_roles WHERE username = ?";
  private static String SQL_GET_PERMISSIONS = "SELECT permission"
      + " FROM roles_permissions WHERE role_name = ?";

  private static String SQL_GET_USERS = "SELECT username"
      + " FROM user_roles WHERE role_name = ?";

  private static String SQL_GET_FUNCTION =
      "SELECT "
          + " f.id 'function#.id', "
          + " f.title 'function#.title' "
          + "FROM "
          + " permissions p "
          + " LEFT JOIN roles_permissions rp ON p.permission_name = rp.permission "
          + " LEFT JOIN permissions_function pf ON pf.pid = p.id "
          + " LEFT JOIN `function` f ON pf.fid = f.id  "
          + "WHERE "
          + " role_name = ?";

  private static boolean status() {
    return null != ShiroKit.getDataSource();
  }

  private static boolean updateUser(String sql, String user, String pwd,
      Map<String, List<String>> authz) {
    boolean result = false;

    if (status()) {
      int rows = Db.update(sql, user,
          ShiroKit.isUseSalt() ? hashPassword(user, pwd) : pwd,
          genSalt(user));
      if (0 == rows) {
        return result;
      }

      Db.update(SQL_CLEAN_ROLES, user);

      if (null != authz) {
        for (Entry<String, List<String>> entry : authz.entrySet()) {
          String role = entry.getKey();
          rows = Db.update(SQL_SET_ROLES, user, role);
          if (0 == rows) {
            Log.w("set " + user + "'s roles failed: " + role);
          }

          List<String> permissions = authz.get(role);
          if (null != permissions) {
            Db.update(SQL_CLEAN_PERMISSIONS, role);

            for (String permission : permissions) {
              rows = Db.update(SQL_SET_PERMISSIONS, role, permission);
              if (0 == rows) {
                Log.w("set " + role + "'s perm failed: " + permission);
              }
            }
          }
        }
      }

      result = true;
    }

    return result;
  }

  /**
   * 添加用户，含数据库操作
   *
   * <pre>
   *  - shiro_use_salt
   *  - shiro_hash_algorithm
   *  - shiro_hash_iterations
   * </pre>
   *
   * @param pwd 密码，是否加盐依赖于配置文件设置
   * @return 是否成功
   */
  public static boolean addUser(String user, String pwd) {
    boolean result = false;

    if (status()) {
      int rows = Db.update(SQL_ADD_USER, user,
          ShiroKit.isUseSalt() ? hashPassword(user, pwd) : pwd,
          genSalt(user));

      result = (1 == rows);
    }

    return result;
  }


  /**
   * 添加用户，含数据库操作，依赖的配置文件属性
   *
   * <pre>
   *  - shiro_use_salt
   *  - shiro_hash_algorithm
   *  - shiro_hash_iterations
   * </pre>
   *
   * @param pwd 密码，计算算法取决于配置文件设置
   * @param authz (角色, (权限))，用户与角色关联，角色与权限关联
   * @return 是否成功
   */
  public static boolean addUser(String user, String pwd,
      Map<String, List<String>> authz) {
    return updateUser(SQL_ADD_USER, user, pwd, authz);
  }

  /**
   * 修改用户密码
   *
   * @param pwd 新密码
   * @return 是否成功
   */
  public static boolean setPwd(String user, String pwd) {
    boolean result = false;

    if (status()) {
      int rows = Db.update(SQL_SET_PWD,
          ShiroKit.isUseSalt() ? hashPassword(user, pwd) : pwd, genSalt(user),
          user);

      result = (1 == rows);
    }

    return result;
  }

  /**
   * 更新用户信息，可修改密码和权限
   *
   * @return 是否成功
   */
  public static boolean setUser(String user, String pwd,
      Map<String, List<String>> authz) {
    return updateUser(SQL_SET_USER, user, pwd, authz);
  }

  /**
   * 删除用户
   *
   * @return 是否成功
   */
  public static boolean deleteUser(String user) {
    boolean result = false;

    if (status()) {
      int rows = Db.update(SQL_DELETE_USER, user);

      result = (1 == rows);
    }

    return result;
  }

  private static boolean setParams(String deleteSql, String updateSql,
      String param, String[] array) {
    boolean result = false;
    boolean[] fullResult = new boolean[array.length];

    if (status()) {
      result = true;
      Db.update(deleteSql, param);

      for (int i = 0; i < array.length; ++i) {
        int rows = Db.update(updateSql, param, array[i]);
        fullResult[i] = (1 == rows);

        // [Neo] .TODO full toJson
        if (!fullResult[i]) {
          result = false;
        }
      }

    }

    return result;
  }

  /**
   * 更新用户所属角色
   *
   * @return 是否成功
   */
  public static boolean setRoles(String user, String... roles) {
    return setParams(SQL_CLEAN_ROLES, SQL_SET_ROLES, user, roles);
  }

  /**
   * 更新角色所包含的用户
   *
   * @return 是否成功
   */
  public static boolean setUsers(String roles, List<String> users) {
    return setParams(SQL_CLEAN_USERS, SQL_SET_USERS, roles,
        users.toArray(new String[0]));
  }

  /**
   * 更新角色的权限
   *
   * @return 是否成功
   */
  public static boolean setPermissions(String role, String... permissions) {
    return setParams(SQL_CLEAN_PERMISSIONS, SQL_SET_PERMISSIONS, role,
        permissions);
  }

  /**
   * 更新用户所属角色
   *
   * @return 是否成功
   */
  public static boolean setRoles(String user, List<String> roles) {
    return setParams(SQL_CLEAN_ROLES, SQL_SET_ROLES, user,
        roles.toArray(new String[0]));
  }

  /**
   * 更新角色的权限
   *
   * @return 是否成功
   */
  public static boolean setPermissions(String role, List<String> permissions) {
    return setParams(SQL_CLEAN_PERMISSIONS, SQL_SET_PERMISSIONS, role,
        permissions.toArray(new String[0]));
  }

  private static List<String> getParams(String sql, String param) {
    List<String> result = new ArrayList<>();

    if (status()) {
      List<String> list = Db.query(sql, param);

      for (String r : list) {
        result.add(r);
      }
    }

    return result;
  }

  /**
   * 获取用户的角色
   */
  public static List<String> getRoles(String user) {
    return getParams(SQL_GET_ROLES, user);
  }

  /**
   * 获取角色包含的权限
   */
  public static List<String> getPermissions(String role) {
    return getParams(SQL_GET_PERMISSIONS, role);
  }

  /**
   * 获取角色包含的用户
   */
  public static List<String> getUsers(String role) {
    return getParams(SQL_GET_USERS, role);
  }

  /**
   * 获取角色包含的权限所属的功能
   */
  public static JSONArray getPermissionsFunction(String role) {
    return Models
        .compressList(Db.find(SQL_GET_FUNCTION, role)).getJSONArray("function");
  }

//  /**
//   * 删除角色中所有用户权限缓存
//   */
//  public static void delUsersCache(String role) {
//    List<String> users = getParams(SQL_GET_USERS, role);
//    for (String s : users) {
//      top.jnaw.jee.utils.Cache.del(s);
//    }
//  }

  /**
   * 删除指定用户的权限缓存
   */
  public static void delUserCache(List<String> users) {
    for (String s : users) {
      top.jnaw.jee.utils.Cache.del(s);
    }
  }

  /**
   * 登录
   *
   * @param remember 是否记住用户名和密码
   * @return 是否正确
   */
  public static boolean login(String user, String pwd,
      boolean remember, boolean noPassword) {
    boolean result = false;

    Subject subject = SecurityUtils.getSubject();
    if (null != subject) {
      EasyTypeToken token;
      if (noPassword) {
        token = new EasyTypeToken(user);
      } else {
        token = new EasyTypeToken(user, pwd);
      }
      token.setRememberMe(remember);
      try {
        subject.login(token);
        result = true;
      } catch (Exception e) {
        // [Neo] Empty
      }
    }

    return result;
  }

  /**
   * 登录
   */
  public static boolean login(String user, String pwd) {
    return login(user, pwd, false, false);
  }

  /**
   * 免密登录
   */
  public static boolean noPasswordLogin(String user) {
    return login(user, "", false, true);
  }

  /**
   * 登出
   */
  public static boolean logout() {
    boolean result = false;

    Subject subject = SecurityUtils.getSubject();
    if (null != subject) {
      subject.logout();
      result = true;
    }

    return result;
  }

  /**
   * 当前用户是否已经登录
   *
   * @return 是否登录
   */
  public static boolean isAuthc() {
    boolean result = false;

    Subject subject = SecurityUtils.getSubject();
    if (null != subject) {
      result = subject.isAuthenticated();
    }

    return result;
  }

  /**
   * 获取通过 Shiros 验证登录的用户名
   */
  public static String getUser() {
    String user = null;

    Subject subject = SecurityUtils.getSubject();
    if (null != subject) {
      final Object p = subject.getPrincipal();
      if (null != p) {
        user = p.toString();
      }
    }

    return user;
  }

  /**
   * 当前用户是否属于某个角色
   *
   * @return 是否属于
   */
  public static boolean hasRole(String role) {
    boolean result = false;
    Subject subject = SecurityUtils.getSubject();

    if (null != subject && StrKit.notBlank(role)) {
      result = subject.hasRole(role);
    }

    return result;
  }

  /**
   * 当前用户是否属于某一些角色
   *
   * @return 是否属于
   */
  public static boolean[] hasRoles(List<String> roles) {
    boolean[] results = null;
    Subject subject = SecurityUtils.getSubject();

    if (null != subject && null != roles) {
      results = subject.hasRoles(roles);
    }

    return results;
  }

  /**
   * 当前用户是否属于某一些角色
   *
   * @return 是否属于
   */
  public static boolean[] hasRoles(String... roles) {
    boolean[] results = null;
    Subject subject = SecurityUtils.getSubject();

    if (null != subject && null != roles) {
      List<String> list = new ArrayList<>();
      for (String r : roles) {
        list.add(r);
      }

      results = subject.hasRoles(list);
    }

    return results;
  }

  /**
   * 当前用户是否具备某个权限
   *
   * @return 是否具备权限
   */
  public static boolean isPermitted(String permission) {
    boolean result = false;
    Subject subject = SecurityUtils.getSubject();

    if (null != subject && StrKit.notBlank(permission)) {
      result = subject.isPermitted(permission);
    }

    return result;
  }

  /**
   * 当前用户是否具备某些权限
   *
   * @return 是否具备权限
   */
  public static boolean[] isPermitted(List<String> permissions) {
    boolean[] results = null;
    Subject subject = SecurityUtils.getSubject();

    if (null != subject && null != permissions) {
      String[] p = new String[permissions.size()];
      for (int i = 0; i < p.length; ++i) {
        p[i] = permissions.get(i);
      }

      results = subject.isPermitted(p);
    }

    return results;
  }

  /**
   * 当前用户是否具备某些权限
   *
   * @return 是否具备权限
   */
  public static boolean[] isPermitted(String... permissions) {
    boolean[] results = null;
    Subject subject = SecurityUtils.getSubject();

    if (null != subject && null != permissions) {
      results = subject.isPermitted(permissions);
    }

    return results;
  }

  /**
   * 根据指定的算法、原文、盐值以及次数计算哈希值
   *
   * @param algorithm 算法
   * @param source 原文
   * @param salt 盐
   * @param iterations 次数
   * @return 哈希结果
   */
  public static String hashWithSalt(String algorithm, String source,
      String salt, int iterations) {
    return new SimpleHash(algorithm, source, salt, iterations).toHex();
  }

  /**
   * 根据原文和盐值计算哈希值，算法和次数依赖配置文件设置
   *
   * <pre>
   *  - shiro_hash_algorithm
   *  - shiro_hash_iterations
   * </pre>
   *
   * @param source 原文
   * @param salt 盐
   * @return 哈希结果
   */
  public static String hashWithSalt(String source, String salt) {
    return hashWithSalt(PropKit.get("shiro_hash_algorithm"), source, salt,
        PropKit.getInt("shiro_hash_iterations"));
  }

  public static String genSalt(String user) {
    return PropKit.get("project_name") + SEPARATOR + user;
  }

  /**
   * 生成针对配置文件的用户密码密文，依赖的配置属性：
   *
   * <pre>
   *  - shiro_hash_algorithm
   *  - shiro_hash_iterations
   * </pre>
   *
   * @param user 用户名
   * @param pwd 密码原文
   * @return 哈希结果
   */
  public static String hashPassword(String user, String pwd) {
    return hashWithSalt(PropKit.get("shiro_hash_algorithm"), pwd, genSalt(user),
        PropKit.getInt("shiro_hash_iterations"));
  }

}
